﻿using DotNetty.Buffers;
using DotNetty.Codecs;
using DotNetty.Handlers.Timeout;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using QQ.Codecs;
using QQ.Packet;
using QQ.Packet.Request;
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Threading.Tasks;
using Tars.Net.Clients;
using Tars.Net.Codecs;
using Tars.Net.Configurations;
using Tars.Net.Metadata;
using QQ.Handler;

namespace QQ.Client
{
    public partial class QQTcpClient : IRpcClient
    {
        private readonly MultithreadEventLoopGroup group = new MultithreadEventLoopGroup();
        private Bootstrap bootstrap;
        private readonly ConcurrentDictionary<EndPoint, IChannel> channels = new ConcurrentDictionary<EndPoint, IChannel>();
        private readonly IEncoder<IByteBuffer> encoder;
        private readonly RpcConfiguration configuration;
        private readonly ClientCallback clientCallback;
        private readonly SSOFrame ssoFrame;
        private readonly HeadPacket headPacket;
        private readonly LoginSigInfo loginSigInfo;
        private ActionChannelInitializer<IChannel> actionChannelInitializer;

        public RpcProtocol Protocol => RpcProtocol.Tcp;

        public QQTcpClient(Codecs.RequestDecoder requestDecoder,HeartBeatHandler heartBeatHandler, ProtobufDecoder protobufDecoder, LoginSigInfo loginSigInfo, HeadPacket headPacket, SSOFrame ssoFrame, ClientCallback clientCallback, wtlogin_login_decoder wtlogin, OICQDecoder oicqDecoder, SSOFrameDecoder ssoFrameDecoder, IncomingDecoder incomingDecoder, RpcConfiguration configuration, IDecoder<IByteBuffer> decoder, IEncoder<IByteBuffer> encoder)
        {
            this.configuration = configuration;
            this.encoder = encoder;
            this.clientCallback = clientCallback;
            this.headPacket = headPacket;
            this.ssoFrame = ssoFrame;
            this.loginSigInfo = loginSigInfo;
            this.actionChannelInitializer = new ActionChannelInitializer<IChannel>(channel =>
            {
                //TODO: ADD ENCODER 
                var pipe = channel.Pipeline;
                pipe.AddLast("packet-frame-encoder", new LengthFieldPrepender(4, true));
                pipe.AddLast("frame-decoder", new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 4, -4, 4));
                pipe.AddLast("incoming-decoder", incomingDecoder);
                pipe.AddLast("sso-decoder", ssoFrameDecoder);
                pipe.AddLast("oicq-decoder", oicqDecoder);
                pipe.AddLast("wtlogin_login-decoder", wtlogin);
                pipe.AddLast("protobuf-decoder", protobufDecoder);
                pipe.AddLast("jce-decoder", requestDecoder);
                pipe.AddLast("idle-handler", new IdleStateHandler(0, 0, 20));
                pipe.AddLast("heart-handler", heartBeatHandler);
                //AddLastPacketDecoderByReflection(pipe);
            });
            StartClient();
        }

        private void StartClient()
        {
             bootstrap = new Bootstrap()
                .Group(group)
                .Channel<TcpSocketChannel>()
                .Option(ChannelOption.TcpNodelay, true)
                .Option(ChannelOption.SoKeepalive, true)
                .Handler(actionChannelInitializer);
        }

        private IChannelPipeline AddLastPacketDecoderByReflection(IChannelPipeline channels)
        {
            throw new Exception("还没写呢，急个蛇皮");
            return channels;
        }

        public async Task SendAsync(object requestObject)
        {
            var channel = await ConnectAsync(configuration.EndPoint);//the default server
            await channel.WriteAndFlushAsync(requestObject);
        }

        private async Task<IChannel> ConnectAsync(EndPoint endPoint)
        {
            if (channels.TryGetValue(endPoint, out IChannel channel)
                && channel.Active && channel.Open)
            {
                Console.WriteLine("发送了已经连接的包");
                return channel;
            }
            else
            {
                Console.WriteLine("发送了重新连接的包");
                channel = await bootstrap.ConnectAsync(endPoint);
                channels.AddOrUpdate(endPoint, channel, (x, y) => channel);
                return channel;
            }
        }

        public Task ShutdownGracefullyAsync(TimeSpan quietPeriod, TimeSpan shutdownTimeout)
        {
            return group.ShutdownGracefullyAsync(quietPeriod, shutdownTimeout);
        }

        public async Task SendAsync(EndPoint endPoint, Request request)
        {
            var channel = await ConnectAsync(endPoint);
            ssoFrame.cmdName = "StatSvc.register";
            ssoFrame.extraData = loginSigInfo.tgt;
            ssoFrame.body = encoder.EncodeRequest(request);

            headPacket.encryptType = HeadPacket.EncryptType.D2KEY;
            headPacket.body = ssoFrame.Serialize();
            headPacket.packetType = 0x0A;
            headPacket.extraData = loginSigInfo.d2;
            await channel.WriteAndFlushAsync(headPacket.Serialize());
            //TODO: SHOULD USE CODEC TO ENCODE THIS 
        }

        public Task Reconnect() => 
        Task.Run(async () => {
            foreach (var item in channels)await item.Value.CloseAsync();
            channels.Clear();
            while(true){
                await Task.Delay(1000);
                try
                {
                    channels[configuration.EndPoint] = await bootstrap.ConnectAsync(configuration.EndPoint);
                    if (loginSigInfo.uin != default)
                    {
                        clientCallback.wtlogin_callback();//relogin
                        System.Console.WriteLine("重新登录中");
                    }
                    break;
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            }
        });
    }
}
